#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <vector>
#include <thread>
#include <unordered_map>
#include <functional>
#include <semaphore.h>
#include <atomic>

#include "public.hpp"
#include "json.hpp"
#include "user.hpp"
#include "group.hpp"

using namespace std;

using json = nlohmann::json;

User curUser;
vector<User> curFriendList;
vector<Group> curGroupList;
bool mainMenuRunning = false;
sem_t rwsem;
atomic_bool loginSucc{false};

void help(int clientfd = 0, string str = "");
void chat(int clientfd, string str);
void addfriend(int clientfd, string str);
void creategroup(int clientfd, string str);
void addgroup(int clientfd, string str);
void groupchat(int clientfd, string str);
void quit(int clientfd, string str);

unordered_map<string, string> commandMap = {
    {"help", "显示所有支持的命令,格式:help"},
    {"chat", "一对一聊天,格式        :chat:friendID:message"},
    {"addfriend", "添加好友,格式     :addfriend:friendID"},
    {"creategroup", "创建群组,格式   :creategroup:groupName:groupDesc"},
    {"addgroup", "加入群组,格式      :addgroup:groupID"},
    {"groupchat", "群组聊天,格式     :groupchat:groupID:message"},
    {"quit", "退出程序,格式          :quit"}};

unordered_map<string, function<void(int, string)>> commandHandlerMap = {
    {"help", help},
    {"chat", chat},
    {"addfriend", addfriend},
    {"creategroup", creategroup},
    {"addgroup", addgroup},
    {"groupchat", groupchat},
    {"quit", quit}};

void showCurUserData()
{
    cout << "=================================" << endl;
    cout << "ID       :" << curUser.getId() << endl;
    cout << "UserName :" << curUser.getName() << endl;
    cout << "State    :" << curUser.getState() << endl;
    cout << "============好友列表=============" << endl;
    for (auto curFriend : curFriendList)
    {
        cout << "ID       :" << curFriend.getId() << endl;
        cout << "UserName :" << curFriend.getName() << endl;
        cout << "State    :" << curFriend.getState() << endl;
    }
    cout << "============群组列表=============" << endl;
    for (auto curGroup : curGroupList)
    {
        cout << "GroupID  :" << curGroup.getGroupId() << endl;
        cout << "GroupName:" << curGroup.getGroupName() << endl;
        cout << "GroupDesc:" << curGroup.getGroupDesc() << endl;
        for (auto curGroupUser : curGroup.getGroupUsers())
        {
            cout << "\tID       :" << curGroupUser.getId() << endl;
            cout << "\tUserName :" << curGroupUser.getName() << endl;
            cout << "\tState    :" << curGroupUser.getState() << endl;
            cout << "\tRole     :" << curGroupUser.getRole() << endl;
        }
    }
    cout << "=================================" << endl;
}

void doLogin(json responsejs)
{
    if (responsejs["errno"].get<int>() == 0)
    {
        curUser.setId(responsejs["id"].get<int>());
        curUser.setName(responsejs["name"]);
        curUser.setState("online");
        if (responsejs.contains("friends"))
        {
            vector<string> friendsStr = responsejs["friends"];
            for (string &str : friendsStr)
            {
                json friendjs = json::parse(str);
                User user;
                user.setId(friendjs["id"].get<int>());
                user.setName(friendjs["name"]);
                user.setState(friendjs["state"]);
                curFriendList.push_back(user);
            }
        }
        if (responsejs.contains("groups"))
        {
            curGroupList.clear();
            vector<string> strVec = responsejs["groups"];
            for (auto &str : strVec)
            {
                json groupjs = json::parse(str);
                Group group;
                group.setGroupId(groupjs["id"].get<int>());
                group.setGroupName(groupjs["name"].get<string>());
                group.setgroupDesc(groupjs["desc"]);
                vector<string> userVec = groupjs["users"];
                for (auto user : userVec)
                {
                    GroupUser groupUser;
                    json groupUserjs = json::parse(user);
                    groupUser.setId(groupUserjs["id"].get<int>());
                    groupUser.setName(groupUserjs["name"]);
                    groupUser.setState(groupUserjs["state"]);
                    groupUser.setRole(groupUserjs["role"]);
                    group.getGroupUsers().push_back(groupUser);
                }
                curGroupList.push_back(group);
            }

            showCurUserData();

            if (responsejs.contains("offlinemsg"))
            {
                // cout << responsejs["offlinemsg"] << endl;
                vector<string> offlineVec = responsejs["offlinemsg"];
                for (auto offlineStr : offlineVec)
                {
                    json js = json::parse(offlineStr);
                    if (ONE_CHAT_MSG == js["msgid"].get<int>())
                    {
                        // cout << js["time"].get<string>() << "[" << js["id"] << "]" << js["name"].get<string>() << "say:" << js["msg"].get<string>() << endl;
                        cout << "[" << js["id"] << "]" << js["name"].get<string>() << " : " << js["msg"].get<string>() << endl;
                        continue;
                    }
                    else if (GROUP_CHAT == js["msgid"].get<int>())
                    {
                        cout << "<" << js["groupid"] << ">"
                             << "[" << js["id"] << "]" << js["name"].get<string>() << " : " << js["msg"].get<string>() << endl;
                        continue;
                    }
                }
            }
        }
    }
}

void doReg(json responsejs)
{
    if (responsejs["errno"].get<int>() != 0)
    {
        cerr << "is already exist, register error" << endl;
    }
    else
    {
        cout << "register success, useris is" << responsejs["id"] << ", do not forget it!" << endl;
    }
}

void readTaskHandler(int clientfd)
{
    while (true)
    {
        char buf[1024] = {0};
        int len = read(clientfd, buf, 1024);
        if (len == 0 || len == -1)
        {
            close(clientfd);
            exit(-1);
        }
        json js = json::parse(buf);
        if (ONE_CHAT_MSG == js["msgid"].get<int>())
        {
            // cout << js["time"].get<string>() << "[" << js["id"] << "]" << js["name"].get<string>() << "say:" << js["msg"].get<string>() << endl;
            cout << "[" << js["id"] << "]" << js["name"].get<string>() << " : " << js["msg"].get<string>() << endl;
            continue;
        }
        else if (GROUP_CHAT == js["msgid"].get<int>())
        {
            cout << "<" << js["groupid"] << ">"
                 << "[" << js["id"] << "]" << js["name"].get<string>() << " : " << js["msg"].get<string>() << endl;
        }
        else if (LOGIN_MSG_ACK == js["msgid"].get<int>())
        {
            doLogin(js);
            sem_post(&rwsem);
        }
        else if (REG_MSG_ACK == js["msgid"].get<int>())
        {
            doReg(js);
            sem_post(&rwsem);
        }
    }
}

void mainMenu(int clientfd)
{
    help();
    char buf[1024] = {0};
    while (mainMenuRunning)
    {
        cin.getline(buf, 1024);

        string commandbuf(buf);
        string command;
        size_t idx = commandbuf.find(':');
        if (idx == -1)
        {
            command = commandbuf;
        }
        else
        {
            command = commandbuf.substr(0, idx);
        }

        auto it = commandHandlerMap.find(command);
        if (it == commandHandlerMap.end())
        {
            cerr << "invalid inpu!1" << endl;
            continue;
        }

        it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - idx)); //存疑
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "command invalid! example:./ChatClient 127.0.0.1 8888" << endl;
    }
    char *ip = argv[1];
    uint16_t port = atoi(argv[2]);

    int clientfd = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_addr.s_addr = inet_addr(ip);
    cliaddr.sin_port = htons(port);

    if (connect(clientfd, (sockaddr *)&cliaddr, sizeof(cliaddr)) == -1)
    {
        cerr << "connect server error";
    }

    sem_init(&rwsem, 0, 0);

    thread readTask(readTaskHandler, clientfd);
    readTask.detach();

    while (true)
    {
        cout << "=================================" << endl;
        cout << "1.login" << endl;
        cout << "2.register" << endl;
        cout << "3.quit" << endl;
        cout << "=================================" << endl;
        cout << "choice  :";
        int choice = 0;
        cin >> choice;
        cin.get();

        switch (choice)
        {
        // msgid(LOGIN_MSG) id password -> server
        // server -> success : msgid errno(0) id name offlinemsg(vector<string>) friends(vector<string>(json:id name state))
        //        -> fail    : msgid errno(!0) errmsg
        case 1:
        {
            int id;
            string password;
            cout << "ID      :";
            cin >> id;
            cin.get();
            cout << "PassWord:";
            cin >> password;
            cin.get();

            json js;
            js["msgid"] = LOGIN_MSG;
            js["id"] = id;
            js["password"] = password;
            string resquest = js.dump();

            int len = write(clientfd, resquest.c_str(), resquest.length() + 1);
            if (len == -1)
            {
                cerr << "send msg err:" << resquest << endl;
            }
            else
            {
                sem_wait(&rwsem);
                if (loginSucc == true)
                {
                    mainMenuRunning = true;
                    mainMenu(clientfd);
                }
            }

            break;
        }
        case 2: // msgid(REG_MSG) name password -> server -> msgid id errno
        {
            string username;
            string password;
            cout << "username:";
            cin >> username;
            cout << "password:";
            cin >> password;
            cin.get();

            json js;
            js["msgid"] = REG_MSG;
            js["name"] = username;
            js["password"] = password;
            string resquest = js.dump();

            int len = write(clientfd, resquest.c_str(), resquest.length() + 1);
            if (len == -1)
            {
                cerr << "send msg err:" << resquest << endl;
            }
            sem_wait(&rwsem);
            break;
        }
        case 3:
            close(clientfd);
            sem_destroy(&rwsem);
            exit(0);
        default:
            cerr << "invalid input" << endl;
            break;
        }
    }
}

void help(int clientfd, string str)
{

    cout << "=================================" << endl;
    cout << "show command list" << endl;
    for (auto &p : commandMap)
    {
        cout << p.first << ":" << p.second << endl;
    }
    cout << "=================================" << endl;
}

// friendID:message
void chat(int clientfd, string str)
{
    size_t idx = str.find(':');
    if (idx == -1)
    {
        cerr << "chat command invalid" << endl;
    }
    int friendID = atoi(str.substr(0, idx).c_str());
    string msg = str.substr(idx + 1, str.size() - idx);

    json js;
    js["to"] = friendID;
    js["msgid"] = ONE_CHAT_MSG;
    js["id"] = curUser.getId();
    js["msg"] = msg;
    js["name"] = curUser.getName();

    string buf = js.dump();

    int len = write(clientfd, buf.c_str(), buf.size() + 1);
    if (len == -1)
    {
        cerr << "send msg fail" << buf << endl;
    }
}

// addfriend:friendID
void addfriend(int clientfd, string str)
{
    int friendID = atoi(str.c_str());
    json js;
    js["msgid"] = ADD_FRIEND;
    js["id"] = curUser.getId();
    js["friendid"] = friendID;

    string buf = js.dump();
    int len = write(clientfd, buf.c_str(), strlen(buf.c_str()) + 1);
    if (len == -1)
    {
        cerr << "add friend fail" << buf << endl;
    }
}
// creategroup:groupName:groupDesc
void creategroup(int clientfd, string str)
{
    cout << str << endl;
    int idx = str.find(':');
    if (idx == -1)
    {
        cerr << "creategroup command invalid!" << endl;
        return;
    }
    string groupName = str.substr(0, idx);
    string groupDesc = str.substr(idx + 1, str.size() - idx);

    json js;
    js["msgid"] = CREATE_GROUP;
    js["id"] = curUser.getId();
    js["groupName"] = groupName;
    js["groupDesc"] = groupDesc;

    string buf = js.dump();
    int len = write(clientfd, buf.c_str(), strlen(buf.c_str()) + 1);
    if (len == -1)
    {
        cerr << "send msg fail" << buf << endl;
    }
}

// groupchat:groupID
void addgroup(int clientfd, string str)
{
    int groupID = atoi(str.c_str());

    json js;
    js["msgid"] = ADD_GROUP;
    js["id"] = curUser.getId();
    js["groupid"] = groupID;

    string buf = js.dump();
    int len = write(clientfd, buf.c_str(), strlen(buf.c_str()) + 1);
    if (len == -1)
    {
        cerr << "send msg fail" << buf << endl;
    }
}
// groupchat:groupID:message
void groupchat(int clientfd, string str)
{
    int idx = str.find(':');
    if (idx == -1)
    {
        cerr << "creategroup command invalid!" << endl;
        return;
    }

    int groupID = atoi(str.substr(0, idx).c_str());
    string message = str.substr(idx + 1, str.size() - idx);

    json js;
    js["msgid"] = GROUP_CHAT;
    js["id"] = curUser.getId();
    js["name"] = curUser.getName();
    js["groupid"] = groupID;
    js["msg"] = message;

    string buf = js.dump();
    int len = write(clientfd, buf.c_str(), strlen(buf.c_str()) + 1);
    if (len == -1)
    {
        cerr << "send group msg fail" << buf << endl;
    }
}

void quit(int clientfd, string str)
{
    json js;
    js["msgid"] = EXIT_MSG;
    js["id"] = curUser.getId();

    string buf = js.dump();
    int len = write(clientfd, buf.c_str(), strlen(buf.c_str()) + 1);
    if (len == -1)
    {
        cerr << "quit msg send fail" << buf << endl;
    }

    mainMenuRunning = false;
}
